package hk.com.fgoproduction.getdroplets.DropboxAPI

import android.app.Activity
import android.content.Context
import android.os.Environment
import android.util.Log
import com.dropbox.core.DbxRequestConfig
import com.dropbox.core.v2.DbxClientV2
import com.dropbox.core.v2.files.*
import hk.com.fgoproduction.getdroplets.Lib.Common.ToolInstance.LogInstance
import hk.com.fgoproduction.getdroplets.Lib.TreeList.TreeList
import hk.com.fgoproduction.getdroplets.Lib.TreeList.TreeNode
import hk.com.fgoproduction.getdroplets.R
import java.io.*
import java.util.*

class AFile(private val ctx: Context) {

    private val TOKEN = lazy{ctx.resources.getString(R.string.token)}
    private val LOG_TAG = "AFile"
    private val ROOT = ""

    private var client = lazy { DbxClientV2(DbxRequestConfig.newBuilder(ctx.resources.getString(R.string.client_identifier)).build(), ctx.getSharedPreferences(TOKEN.value, Activity.MODE_PRIVATE).getString("access", null)) }

    private val showLog = true
    private var folderList: ListFolderResult? = null
    private val rootNode = TreeNode(ROOT, ROOT, TreeNode.LEVEL_TOP, null, true)

    fun uploadFile(path: String) {
        val file = File(path)
        LogInstance.log(LOG_TAG,
                "File name: " + file.name,
                LogInstance.LogType.DEBUG,
                showLog)

        if (file != null) {
            try {
                uploadFileBody(file)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        } else {
            LogInstance.log(LOG_TAG, "UL path invalid", LogInstance.LogType.ERROR, showLog)
        }

        LogInstance.log(LOG_TAG, " UL Success", LogInstance.LogType.DEBUG, showLog)
    }

    private fun uploadFileBody(file: File) {
        val `in` = FileInputStream(file)
        client.value.files()
                .uploadBuilder("/" + file.name)
                .withMode(WriteMode.OVERWRITE)
                .uploadAndFinish(`in`)
    }

    fun getUploader(path: String, mode: WriteMode) : UploadUploader {
        return client.value.files().uploadBuilder(path).withMode(mode).start()
    }

    fun getUploadStream(uploader: UploadUploader) : OutputStream {
        return uploader.outputStream
    }

    fun uploadFinish(uploader: UploadUploader) {
        uploader.finish()
    }

    fun downloadFile(fullPath: String) {
        val metadata = client.value.files().getMetadata(fullPath)
        when (metadata) {
            is FolderMetadata -> return
            is FileMetadata -> downloadFile(metadata)
        }
    }

    fun getDownloadStream(path: String) : InputStream? {
        try {
            val metadata = client.value.files().getMetadata(path)

            when (metadata) {
                is FolderMetadata -> return null
                is FileMetadata -> return getDownloadStream(metadata)
            }
        } catch (e: Exception) {
            throw e
        }
        return null
    }

    fun getDownloadStream(metadata: FileMetadata) : InputStream {
        return client.value.files().download(metadata.pathLower, metadata.rev).inputStream
    }

    fun downloadFile(metadata: FileMetadata) {
        try {
            if (folderList == null) folderList = getFolderList()
            else if (folderList?.entries!!.isEmpty()) folderList = getFolderList()

            val path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
            val file = File(path, metadata.name)

            if (!path.exists()) {
                if (!path.mkdirs()) {
                    throw Exception("Cannot create directory")
                }
            } else if (!path.isDirectory) {
                throw Exception("Download path error")
            }

            try {
                val out = FileOutputStream(file)
                client.value.files().download(metadata.pathLower, metadata.rev).download(out)
            } catch (e: IOException) {
                throw e
            }
        } catch (e: Exception) {
            throw e
        }
        LogInstance.log(LOG_TAG, "DL Finish", LogInstance.LogType.DEBUG, showLog)
    }

    fun getFolderList(path: String = ROOT): ListFolderResult {
        try {
            while (true) {
                folderList = client.value.files().listFolder(path)
                if (!(folderList as ListFolderResult).hasMore) {
                    break
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return folderList as ListFolderResult
    }

    fun getFolderList(dirTree: TreeList, path: String = ROOT): ListFolderResult {
        var dirNode: TreeNode = rootNode
        if (dirTree.getRoot() === null)
            setRoot(dirTree, dirNode)
        else
            dirNode = dirTree.get(path) as TreeNode

        try {
            iterateFolder(dirTree, dirNode, path)
        } catch (e: IOException) {
            e.printStackTrace()
        }

        return (folderList as ListFolderResult)
    }

    /**
     * IO - Network
     * @throws IOException
     */
    @Throws(IOException::class)
    private fun iterateFolder(dirTree: TreeList, dirNode: TreeNode, path: String) {
        while (true) {
            folderList = client.value.files().listFolder(path)
            for (metadata: Metadata in (folderList as ListFolderResult).entries)
                dirTree.put(dirTree.nodeFactory(dirNode, metadata))

            if (!(folderList as ListFolderResult).hasMore) {
                break
            }
        }
    }

    /**
     * IO
     */
    private fun setRoot(tree: TreeList, rootNode: TreeNode) {
        tree.setRoot(rootNode)
    }

    fun getMetadata(path: String): Metadata {
        return client.value.files().getMetadata(path)
    }

    fun getFileSize(path: String) : Long {
        val metadata = client.value.files().getMetadata(path)
        when (metadata) {
            is FileMetadata -> return metadata.size
            else -> return 0
        }
    }

    @Throws(Exception::class)
    fun isDropboxOk() {
        try {
            LogInstance.log("$this", ctx.resources.getString(R.string.client_identifier), LogInstance.LogType.VERBOSE, showLog)
            val name = client.value.users().currentAccount.name.displayName
        } catch (e: Exception) {
            throw e
        }
    }

    fun toArrayList(path: String, dirTree: TreeList): ArrayList<String> {
        val tempFolder = ArrayList<String>()
        val tempFile = ArrayList<String>()
        val temp = ArrayList<String>()
        if (dirTree.getRoot() === null) return temp

        if (dirTree.get(path)?.path != ROOT) {
            temp.add("/")
            temp.add("../")
        }

        val list = dirTree.searchList(path, dirTree.getChildren())?.getChildren() as LinkedHashMap

        tempFolder.addAll(
                list.filter { it.value.getRoot()?.isDirectory as Boolean }
                        .map { it.value.getRoot()!!.name+"/" }
        )

        tempFile.addAll(
                list.filterNot { it.value.getRoot()?.isDirectory as Boolean }
                        .map{ it.value.getRoot()!!.name }
        )

        /*list{...} ===
        for ((key, value) in list) {
            val node = value.getRoot() as TreeNode
            if (node.isDirectory)
                tempFolder.add(node.name + "/")
            else
                tempFile.add(node.name)
        }
        */

        Collections.sort(tempFolder)
        Collections.sort(tempFile)
        temp.addAll(tempFolder)
        temp.addAll(tempFile)

        return temp
    }

    fun toMap(path: String, dirTree: TreeList): LinkedHashMap<String, String> {
        val temp = LinkedHashMap<String, String>()
        if (dirTree.getRoot() === null) return temp

        val src = dirTree.searchList(path, dirTree.getChildren()) as TreeList
        val list = src.getChildren()
        temp.putAll(
                list.mapValues { it.value.getRoot() }
                        .values
                        .associate { it!!.name to it.path }
        )
        /*
        for ((key, value) in list) {
            val node = value.getRoot() as TreeNode
            temp.put(node.name, node.path)
        }
        */

        if (src != dirTree) {
            temp.put("/", ROOT)
            temp.put("../", src.getRoot()?.parentPath as String)
        }

        return temp
    }
}
